package com.snow.jpa;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.log.LogFactory;
import com.snow.conditions.*;
import com.snow.exception.CommonRuntimeException;
import com.snow.jpa.model.IBasePageParams;
import com.snow.model.ILoginUser;
import com.snow.service.IAuthComponent;
import com.snow.view.PageData;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author by 13119
 */
public interface BaseRepository<E extends IBaseEntity<ID>, ID> extends JpaRepositoryImplementation<E, ID> {

    default Specification<E> toSpec(QueryCondition conditions) {
        return toSpec(conditions, LambdaQueryBuilder.getEnableDeptDataScope(), LambdaQueryBuilder.getEnableTenantFilter());
    }

    default Specification<E> toSpec(QueryCondition conditions, boolean enableTenantFilter) {
        return toSpec(conditions, LambdaQueryBuilder.getEnableDeptDataScope(), enableTenantFilter);
    }

    /**
     * 根据QueryConditions构造查询条件
     */
    default Specification<E> toSpec(QueryCondition conditions, boolean enableDeptDataFilter, boolean enableTenantFilter, QueryOrder... orders) {
        return (root, query, build) -> {
            Predicate predicate = buildPredicateByCondition(conditions, root, query, build, enableDeptDataFilter, enableTenantFilter);
            if (orders != null && orders.length > 0) {
                setOrders(root, query, build, orders);
            }
            return predicate;
        };
    }

    /**
     * 根据注解构造查询条件
     */
    default Specification<E> toSpec(Object obj) {
        boolean enableDeptDataScope = LambdaQueryBuilder.getEnableDeptDataScope();
        boolean enableTenantFilter = LambdaQueryBuilder.getEnableTenantFilter();
        return (root, query, build) -> buildPredicateByAnnotation(obj, root, query, build, enableDeptDataScope, enableTenantFilter);
    }

    /**
     * 根据注解构造进行分页查询
     */
    default PageData<E> page(IBasePageParams params) {
        return PageData.of(findAll(toSpec(params), PageRequest.of(params.getPage() - 1, params.getSize())));
    }


    /**
     * 根据 condition 构建查询条件
     *
     * @param condition 查询条件
     */
    default Predicate buildPredicateByCondition(QueryCondition condition, Root<E> root, CriteriaQuery<?> query, CriteriaBuilder build, boolean enableDeptDataScope, boolean enableTenantFilter) {
        Object value = condition.getValue();
        //如果非查询组 或者是子查询直接获取
        if (!condition.getGroupCondition() || condition.getSubQuery()) {
            return getPredicate(root, query, build, condition);
        }
        List<Predicate> predicates = new ArrayList<>();
        List<QueryCondition> conditions;
        if (!(value instanceof List)) {
            throw new RuntimeException("当字段未查询条件组时，值必须为List类型");
        }
        try {
            conditions = (List<QueryCondition>) value;
        } catch (Exception e) {
            throw new RuntimeException("当查询条件组为true时，值必须为List<QueryCondition>");
        }
        for (QueryCondition item : conditions) {
            predicates.add(buildPredicateByCondition(item, root, query, build, enableDeptDataScope, enableTenantFilter));
        }
        Predicate predicate = null;
        if (condition.getLinkType().equals(LinkType.AND)) {
            predicate = build.and(predicates.toArray(new Predicate[]{}));
        } else {
            build.or(predicates.toArray(new Predicate[]{}));
        }
        return buildExtraPredicates(predicate, build, root, query, enableDeptDataScope, enableTenantFilter);

    }

    default Predicate buildExtraPredicates(Predicate predicate, CriteriaBuilder build, Root<E> root, CriteriaQuery<?> query, boolean enableDeptDataScope, boolean enableTenantFilter) {
        List<Predicate> extraPredicates = new ArrayList<>();
        Method method = ReflectUtil.getMethod(SimpleJpaRepository.class, "getDomainClass");
        method.setAccessible(true);
        Object invoke = null;
        try {
            invoke = method.invoke(AopProxyUtils.getSingletonTarget(this));
        } catch (Exception e) {
            LogFactory.get().error("当前entity类型获取失败，部分功能受限：tenant租户隔离等。如有疑问，请联系管理员处理");
            return predicate;
        }
        if (enableTenantFilter && ITenantEntity.class.isAssignableFrom((Class) invoke)) {
            LogFactory.get().debug("本条查询已开启租户隔离功能，添加where条件");
            IAuthComponent authComponent = SpringUtil.getBean(IAuthComponent.class);
            ILoginUser currentUser = null;
            try {
                currentUser = authComponent.getCurrentUser();
            } catch (Exception e) {
                LogFactory.get().warn("用户信息获取出现异常,将不进行租户数据过滤", e);
            }
            if (currentUser != null) {
                extraPredicates.add(build.equal(root.get("tenantId"), currentUser.getTenantId()));
            } else {
                LogFactory.get().warn("未获取到登录用户信息，将不进行租户数据过滤");
            }
        } else {
            LogFactory.get().debug("本条查询未开启租户隔离功能，enableTenantFilter : " + enableTenantFilter + ",  is TenantEntity: " + ITenantEntity.class.isAssignableFrom((Class) invoke));
        }
        if (IDeletedEntity.class.isAssignableFrom((Class) invoke)) {
            extraPredicates.add(build.equal(root.get("deleted"), false));
        }
        if (enableDeptDataScope && IDeptDataScopeEntity.class.isAssignableFrom((Class) invoke)) {
            IAuthComponent authComponent = SpringUtil.getBean(IAuthComponent.class);
            ILoginUser currentUser = null;
            try {
                currentUser = authComponent.getCurrentUser();
            } catch (Exception e) {
                LogFactory.get().warn("用户信息获取出现异常,将不根据用户进行数据权限过滤", e);
            }
            if (currentUser == null || currentUser.getDataScope() == 0) {
                LogFactory.get().warn("未获取到登录用户信息或该用户可获取所有数据，将不根据用户进行数据权限过滤");
            } else {
                if (currentUser.getDataScope() == 1) {
                    extraPredicates.add(build.equal(root.get("createId"), currentUser.getId()));
                } else if (currentUser.getDataScope() == 2) {
                    extraPredicates.add(build.equal(root.get("deptId"), currentUser.getDeptId()));
                } else if (currentUser.getDataScope() == 3) {
//                    Subquery<Integer> subQuery = query.subquery(Integer.class);
//                    extraPredicates.add(build.in(root.get("deptId"), ))
                    throw new CommonRuntimeException("暂不支持");
                } else {
                    LogFactory.get().error("当前用户数据权限类型定义失败，请及时处理");
                }
            }
        }
        if (extraPredicates.isEmpty()) {
            return predicate;
        } else {
            return build.and(predicate, build.and(extraPredicates.toArray(new Predicate[]{})));
        }
    }

    /**
     * 构建查询条件入口根据注解
     */
    default Predicate buildPredicateByAnnotation(Object obj, Root<E> root, CriteriaQuery<?> query, CriteriaBuilder build, boolean enableDeptDataScope, boolean enableTenantFilter) {
        List<Predicate> predicates = new ArrayList<>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            QueryConditionField fieldCondition = field.getAnnotation(QueryConditionField.class);
            if (fieldCondition == null) {
                // 字段未标注查询条件，跳过
                continue;
            }
            Object fieldValue = getFieldValue(obj, field);
            if (fieldValue == null || (StrUtil.isBlankIfStr(fieldValue)) || ArrayUtil.isEmpty(fieldValue)) {
                // 查询条件未赋值，跳过
                continue;
            }
            if (!fieldCondition.groupCondition() || fieldCondition.subQuery()) {
                Predicate predicate = getPredicate(root, query, build, new QueryCondition(StrUtil.blankToDefault(fieldCondition.value(), field.getName()), fieldValue, fieldCondition));
                if (predicate != null) {
                    predicates.add(predicate);
                }
                continue;
            }
            //条件查询组递归构建
            predicates.add(buildPredicateByAnnotation(fieldValue, root, query, build, enableDeptDataScope, enableTenantFilter));
        }
        QueryDescription queryDescription = obj.getClass().getAnnotation(QueryDescription.class);
        Predicate predicate;
        if (queryDescription == null || queryDescription.link().equals(LinkType.AND)) {
            predicate = build.and(predicates.toArray(new Predicate[]{}));
        } else {
            predicate = build.or(predicates.toArray(new Predicate[]{}));
        }
        query.where(buildExtraPredicates(predicate, build, root, query, enableDeptDataScope, enableTenantFilter));
        if (queryDescription != null && queryDescription.sort().length > 0) {
            setOrders(root, query, build, queryDescription.sort());
        }
        return query.getRestriction();
    }


    /**
     * 设置排序
     */
    static <E> void setOrders(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder build, com.snow.conditions.Order... orders) {
        if (orders == null) {
            return;
        }
        QueryOrder[] array = Arrays.stream(orders).map(e -> {
            if (Objects.requireNonNull(e.type()) == QueryOrderType.asc) {
                return QueryOrder.asc(e.value());
            }
            return QueryOrder.desc(e.value());
        }).collect(Collectors.toList()).toArray(new QueryOrder[]{});
        setOrders(root, query, build, array);
    }

    static <E> void setOrders(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder build, QueryOrder... orders) {
        if (orders == null) {
            return;
        }
        Order[] realOrders = new Order[orders.length];
        for (int i = 0; i < orders.length; i++) {
            QueryOrder currentOrder = orders[i];
            switch (currentOrder.getType()) {
                case asc:
                    realOrders[i] = build.asc(root.get(currentOrder.getColumn()));
                    break;
                case desc:
                    realOrders[i] = build.desc(root.get(currentOrder.getColumn()));
            }
        }
        query.orderBy(realOrders);
    }

    default Predicate buildSubQueryPredicate(CriteriaBuilder criteriaBuilder, Root<?> root, QueryCondition condition) {
        Field[] fields = condition.getValue().getClass().getDeclaredFields();
        List<Predicate> predicates = new ArrayList<>();
        for (Field field : fields) {
            QueryConditionField fieldCondition = field.getAnnotation(QueryConditionField.class);
            if (fieldCondition == null) {
                // 字段未标注查询条件，跳过
                continue;
            }
            Object fieldValue = getFieldValue(condition.getValue(), field);
            if (fieldValue == null || (StrUtil.isBlankIfStr(fieldValue)) || ArrayUtil.isEmpty(fieldValue)) {
                // 查询条件未赋值，跳过
                continue;
            }
            if (fieldCondition.groupCondition()) {
                //条件查询组递归构建
                predicates.add(buildSubQueryPredicate(criteriaBuilder, root, condition));
            }
            Predicate predicate = getPredicate(fieldCondition.type(), criteriaBuilder, root, StrUtil.blankToDefault(fieldCondition.value(), field.getName()), fieldValue, fieldCondition.dateOffset());
            if (predicate != null) {
                predicates.add(predicate);
            }
        }
        if (condition.getLinkType() == LinkType.AND) {
            return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
        } else {
            return criteriaBuilder.or(predicates.toArray(new Predicate[]{}));
        }
    }


    default Predicate getPredicate(ConditionType type, CriteriaBuilder criteriaBuilder, Root<?> root, String column, Object value, DayTimeOffset dayTimeOffset) {
        switch (type) {
            case in:
                if (value instanceof Subquery) {
                    // 子查询直接，赋值
                    return criteriaBuilder.in(root.get(column)).value((Subquery<?>) value);
                }
                // 非子查询，校验类型
                CriteriaBuilder.In<Object> inCriteria = criteriaBuilder.in(root.get(column));
                if (ArrayUtil.isArray(value)) {
                    for (Object item : ArrayUtil.wrap(value)) {
                        inCriteria.value(item);
                    }
                    return inCriteria;
                }
                if (value instanceof Collection) {
                    for (Object o : (Collection<?>) value) {
                        inCriteria.value(o);
                    }
                    return inCriteria;
                }
                throw new RuntimeException("In条件需要为数组或Collection类型");
            case notIn:
                CriteriaBuilder.In<Object> inCb = criteriaBuilder.in(root.get(column));
                if (ArrayUtil.isArray(value)) {
                    for (Object o : ArrayUtil.wrap(value)) {
                        inCb.value(o);
                    }
                    return criteriaBuilder.not(inCb);
                }
                if (value instanceof Collection) {
                    for (Object o : (Collection<?>) value) {
                        inCb.value(o);
                    }
                    return criteriaBuilder.not(inCb);
                }
                throw new RuntimeException("not In条件需要为数组或Collection类型");
            case between:
                Object firstValue = null;
                Object secondValue = null;
                if (value instanceof Collection && ((Collection) value).size() == 2) {
                    Collection values = (Collection) value;
                    firstValue = values.toArray()[0];
                    secondValue = values.toArray()[1];
                } else if (ArrayUtil.isArray(value) && ArrayUtil.length(value) == 2) {
                    firstValue = ArrayUtil.get(value, 0);
                    secondValue = ArrayUtil.get(value, 1);
                } else {
                    throw new CommonRuntimeException("between 的值应该为 长度为2 的集合或数组");
                }
                if (firstValue == null || secondValue == null) {
                    throw new CommonRuntimeException("between 的值应该为 长度为2 的集合或数组，且元素不可为空");
                }
                if (firstValue instanceof Integer) {
                    return criteriaBuilder.between(root.get(column), (Integer) firstValue, (Integer) secondValue);
                }
                if (firstValue instanceof Long) {
                    return criteriaBuilder.between(root.get(column), (Long) firstValue, (Long) secondValue);
                }
                if (firstValue instanceof Date) {
                    return criteriaBuilder.between(root.get(column), dayTimeOffset.of((Date) firstValue), dayTimeOffset.of((Date) secondValue));
                }
                if (firstValue instanceof String) {
                    return criteriaBuilder.between(root.get(column), DateUtil.parse((String) firstValue), DateUtil.parse((String) secondValue));
                }
                throw new CommonRuntimeException("between暂不支持的类型:" + value.getClass());
            case equal:
                return criteriaBuilder.equal(root.get(column), value);
            case notEqual:
                return criteriaBuilder.notEqual(root.get(column), value);
            case like:
                return criteriaBuilder.like(root.get(column), "%" + value + "%");
            case likeLeft:
                return criteriaBuilder.like(root.get(column), "%" + value);
            case likeRight:
                return criteriaBuilder.like(root.get(column), value + "%");
            case notLike:
                return criteriaBuilder.notLike(root.get(column), "%" + value + "%");
            case greaterThan:
                if (value instanceof String) {
                    return criteriaBuilder.greaterThan(root.get(column), (String) value);
                }
                if (value instanceof Integer) {
                    return criteriaBuilder.greaterThan(root.get(column), (Integer) value);
                }
                if (value instanceof Long) {
                    return criteriaBuilder.greaterThan(root.get(column), (Long) value);
                }
                if (value instanceof Date) {
                    return criteriaBuilder.greaterThan(root.get(column), dayTimeOffset.of((Date) value));
                }
            case greaterThanOrEqual:
                if (value instanceof String) {
                    return criteriaBuilder.greaterThanOrEqualTo(root.get(column), (String) value);
                }
                if (value instanceof Integer) {
                    return criteriaBuilder.greaterThanOrEqualTo(root.get(column), (Integer) value);
                }
                if (value instanceof Long) {
                    return criteriaBuilder.greaterThanOrEqualTo(root.get(column), (Long) value);
                }
                if (value instanceof Date) {
                    return criteriaBuilder.greaterThanOrEqualTo(root.get(column), dayTimeOffset.of((Date) value));
                }
            case lessThan:
                if (value instanceof String) {
                    return criteriaBuilder.lessThan(root.get(column), (String) value);
                }
                if (value instanceof Integer) {
                    return criteriaBuilder.lessThan(root.get(column), (Integer) value);
                }
                if (value instanceof Long) {
                    return criteriaBuilder.lessThan(root.get(column), (Long) value);
                }
                if (value instanceof Date) {
                    return criteriaBuilder.lessThan(root.get(column), dayTimeOffset.of((Date) value));
                }
            case lessThanOrEqual:
                if (value instanceof String) {
                    return criteriaBuilder.lessThanOrEqualTo(root.get(column), (String) value);
                }
                if (value instanceof Integer) {
                    return criteriaBuilder.lessThanOrEqualTo(root.get(column), (Integer) value);
                }
                if (value instanceof Long) {
                    return criteriaBuilder.lessThanOrEqualTo(root.get(column), (Long) value);
                }
                if (value instanceof Date) {
                    return criteriaBuilder.lessThanOrEqualTo(root.get(column), dayTimeOffset.of((Date) value));
                }
        }
        throw new RuntimeException("子查询尚未开发：条件：【{}】,类型：【{}】，请联系架构师");
    }

    /**
     * 获取查询条件
     *
     * @param root            构造器
     * @param criteriaBuilder 条件器
     * @return 查询条件
     */
    default Predicate getPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, QueryCondition condition) {
        Subquery<?> subquery = null;
        if (condition.getSubQuery()) {
            if (condition.getValue() instanceof SubQueryCondition) {
                return ((SubQueryCondition) condition.getValue()).buildSubQueryPredicate(root, query, criteriaBuilder);
            }
            if (condition.getGroupCondition()) {
                subquery = query.subquery(condition.getSubFieldType());
                Root<?> from = subquery.from(condition.getSubFrom());
                subquery.select(from.get(condition.getSubField()));
                subquery.where(buildSubQueryPredicate(criteriaBuilder, from, condition));
                return getPredicate(condition.getType(), criteriaBuilder, root, condition.getColumn(), subquery, DayTimeOffset.NONE);
            } else {
                subquery = query.subquery(condition.getSubFieldType());
                Root<?> from = subquery.from(condition.getSubFrom());
                subquery.select(from.get(condition.getSubField()));
                subquery.where(getPredicate(condition.getSubQueryType(), criteriaBuilder, from, condition.getSubField(), condition.getValue(), condition.getDayTimeOffset()));
                return getPredicate(condition.getType(), criteriaBuilder, root, condition.getColumn(), subquery, condition.getDayTimeOffset());
            }
        } else {
            return getPredicate(condition.getType(), criteriaBuilder, root, condition.getColumn(), condition.getValue(), condition.getDayTimeOffset());
        }

    }

    /**
     * 获取注解至
     *
     * @param data
     * @param field
     * @return
     */
    default Object getFieldValue(Object data, Field field) {
        try {
            return ReflectUtil.getFieldValue(data, field);
        } catch (UtilException e) {
            Method getMethod = ReflectUtil.getMethodIgnoreCase(data.getClass(), "get" + field.getName());
            return ReflectUtil.invoke(data, getMethod);
        }
    }

    /**
     * lambda查询
     * 支持  findAll 查询
     * 支持 常见的 equal in 查询
     */
    default LambdaQueryBuilder<E, ID> lambdaQuery() {
        return new LambdaQueryBuilder<>(this);
    }


    default E findBy(String column, Object value) {
        return lambdaQuery().equal(column, value).one();
    }

    default List<E> findAllBy(String column, Object value) {
        return lambdaQuery().equal(column, value).findAll();
    }

    default List<E> findAllIn(String column, Object... value) {
        return lambdaQuery().in(column, value).findAll();
    }

    default List<E> findAllBy(String column, Object value, boolean enableTenantFilter) {
        return lambdaQuery().equal(column, value).findAll(enableTenantFilter);
    }

    default List<?> hqlQuery(String hql, Object... params) {
        Query query = SpringUtil.getBean(EntityManager.class).createQuery(hql);
        for (int i = 0; i < params.length; i++) {
            query.setParameter(i + 1, params[i]);
        }
        return query.getResultList();
    }

    default HqlQueryBuilder hql(Class<E> entityClass, String alias) {
        return hql(entityClass, alias, null);
    }

    default HqlQueryBuilder hql(Class<E> entityClass, String alias, ILoginUser loginUser) {
        return HqlQueryBuilder.builder(HqlQueryBuilder.getEntityName(entityClass), alias, loginUser);
    }

    /**
     * alias = m
     */
    default HqlQueryBuilder hql(Class<E> entityClass) {
        return hql(entityClass, "m");
    }

}
